﻿//copyright © kaveh.shahbazian@gmail.com and http://www.codeplex.com/site/users/view/Cread 2010-2011 (see license.txt)
// usings
using System;
using System.Windows;
using System.Windows.Forms;
using Funtastic.Windows;
using Microsoft.Windows.Controls.Ribbon;
using System.IO;
using System.Text;
using System.Diagnostics;
using System.Threading;
using System.Windows.Input;
using ICSharpCode.AvalonEdit.Highlighting;
using System.Xml;
using System.Windows.Controls;

// aliases
using DataFormats = System.Windows.DataFormats;
using MessageBox = System.Windows.MessageBox;
using KeyEventArgs = System.Windows.Input.KeyEventArgs;

namespace Funtastic
{
    public partial class MainWindow : RibbonWindow
    {
        #region vars
        // ui
        OpenFileDialog _openFileDialog;
        SaveFileDialog _saveFileDialog;
        // logic
        bool _scriptChanged = false; // has the document changed?
        string _latestfile = ""; // latest file worked with in Funtastic
        // fsharp interactive
        static readonly string Space4 = new string(' ', 4);
        Process _fsi;
        // input
        Key? _previousKey;
        Key? _currentKey;
        #endregion

        public MainWindow()
        {
            InitializeComponent();
            InitBase();
        }

        public MainWindow(string commandLineArgs)
        {
            InitializeComponent();
            InitBase();

            // test for file
            if (File.Exists(commandLineArgs))
            {
                // load file
                OpenFile(commandLineArgs);
                // refreshing window
                RefreshWindowAfterOpen(commandLineArgs);
            }
        }

        private void InitBase()
        {
            // Insert code required on object creation below this point.
            Loaded += MainWindow_Loaded;
            Closing += Window_Closing;

            // other events
            btnsave.Click += btnsave_Click;
            btnopen.Click += btnopen_Click;
            btnsaveas.Click += btnsaveas_Click;
            btnnew.Click += btnnew_Click;
            comboLayoutGallery.SelectionChanged += comboLayoutGallery_SelectionChanged;

            avaEditor.TextChanged += avaEditor_TextChanged;
            tbOutput.TextChanged += tbOutput_TextChanged;
        }

        #region OpenSave
        void btnnew_Click(object sender, RoutedEventArgs e)
        {
            avaEditor.Text = "";
            _latestfile = "";
            _scriptChanged = false;
            Title = "Funtastic";
        }

        private void recentItems_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            // save work?
            if (_scriptChanged)
            {
                var question = System.Windows.Forms.MessageBox.Show(Properties.Resources.MainWindow_btnopen_Click_OpenAnyway, "Funtastic",
                    MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
                if (question == System.Windows.Forms.DialogResult.No)
                    return;
            }

            string filename = Settings.RecentDocuments[recentItems.SelectedIndex].FullName;
            if (File.Exists(filename))
            {
                OpenFile(filename);

                // refreshing window
                Title = "Funtastic - " + filename;
                _scriptChanged = false;
                _latestfile = filename;
            }
            else
            {
                MessageBox.Show("The file doesn't exist any more!", "Funtatic", MessageBoxButton.OK, MessageBoxImage.Exclamation);
            }
        }

        void btnsaveas_Click(object sender, RoutedEventArgs e)
        {
            _saveFileDialog.ShowDialog();
        }

        private void SaveToFile(string file)
        {
            using (var writer = new StreamWriter(file, false, Encoding.UTF8))
                writer.Write(avaEditor.Text);

            // refreshing window
            _scriptChanged = false;
        }

        void btnopen_Click(object sender, RoutedEventArgs e)
        {
            if (_scriptChanged)
            {
                var question = System.Windows.Forms.MessageBox.Show(Properties.Resources.MainWindow_btnopen_Click_OpenAnyway, "Funtastic",
                    MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
                if (question == System.Windows.Forms.DialogResult.No)
                    return;
            }
            _openFileDialog.ShowDialog();
        }

        void btnsave_Click(object sender, RoutedEventArgs e)
        {
            if (_latestfile != "")
                SaveToFile(_latestfile);
            else
            {
                _saveFileDialog.ShowDialog();
                SelectRecentDocument();
            }
        }

        private void _openFileDialog_FileOk(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (File.Exists(_openFileDialog.FileName))
            {
                OpenFile(_openFileDialog.FileName);

                // refreshing window
                RefreshWindowAfterOpen(_openFileDialog.FileName);
            }
        }

        private void RefreshWindowAfterOpen(string filename)
        {
            // refreshing window
            Title = "Funtastic - " + filename;
            _scriptChanged = false;
            _latestfile = filename;
            SelectRecentDocument();
        }

        private void SelectRecentDocument()
        {
            if (Settings.AddRecentDocument(new Document() { Date = DateTime.Now, FullName = _latestfile }))
                recentItems.SelectedIndex = recentItems.Items.Count - 1;
            else
            {
                for (int i = 0; i < Settings.RecentDocuments.Count; i++)
                {
                    if (Settings.RecentDocuments[i].FullName == _latestfile)
                    {
                        recentItems.SelectedIndex = i;
                        break;
                    }
                }
            }
        }

        private void _saveFileDialog_FileOk(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (_saveFileDialog.FileName != "")
            {
                SaveToFile(_saveFileDialog.FileName);

                // refreshing window
                _scriptChanged = false;
                Title = "Funtastic - " + _saveFileDialog.FileName;
                _latestfile = _saveFileDialog.FileName;
                SelectRecentDocument();
            }
        }

        private void OpenFile(string fileName)
        {
            using (var sr = new StreamReader(new FileStream(fileName, FileMode.Open)))
            {
                avaEditor.Text = sr.ReadToEnd();
                sr.Close();
            }
        }

        void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (_scriptChanged)
            {
                var question = System.Windows.Forms.MessageBox.Show(Properties.Resources.MainWindow_Window_Closing_Question, "Funtastic",
                    MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
                if (question == System.Windows.Forms.DialogResult.No)
                {
                    e.Cancel = true;
                    return;
                }
            }

            if (_openFileDialog != null)
                _openFileDialog.Dispose();

            if (_saveFileDialog != null)
                _saveFileDialog.Dispose();

            ShutdownFsi();

            Settings.SaveValues();
        }
        #endregion

        #region FSharpInteractive
        private void ShutdownFsi()
        {
            if (_fsi != null)
                try
                {
                    _fsi.StandardInput.WriteLine("#quit;;");
                    _fsi.Close();
                }
                catch (Exception)
                { }
            try
            {
                if (_fsi != null)
                    if (!_fsi.HasExited && !_fsi.WaitForExit(1000))
                    {
                        _fsi.Kill();
                    }
            }
            catch (Exception)
            { }
        }

        void SendSelectedCodeToFsi()
        {
            if (_fsi.HasExited)
                tbOutput.AppendText("\nFsi.exe seems to be crashed...");
            _fsi.StandardInput.Write(avaEditor.SelectedText + ";;\n\r\r");
            _fsi.StandardInput.Flush();
        }

        private void SendCodeToFsi()
        {
            if (_fsi.HasExited)
                tbOutput.AppendText("\nFsi.exe seems to be crashed...");
            _fsi.StandardInput.Write(avaEditor.Text + ";;\n\r\r");
            _fsi.StandardInput.Flush();
        }

        private void PrepareFsi()
        {
            ShutdownFsi();

            // look for 64bit/32bit
            if (!File.Exists(Settings.FsPath + "\\Fsi.exe") || Settings.FsPath == "")
            {
                if (File.Exists("C:\\Program Files\\Microsoft F#\\v4.0\\Fsi.exe")) // standard path on vista/7-32bit machines
                    Settings.FsPath = "C:\\Program Files\\Microsoft F#\\v4.0";
                if (File.Exists("C:\\Program Files (x86)\\Microsoft F#\\v4.0\\Fsi.exe")) // standard path on vista/7-64bit machines
                    Settings.FsPath = "C:\\Program Files (x86)\\Microsoft F#\\v4.0";
                if (File.Exists("C:\\Programme\\Microsoft F#\\v4.0\\Fsi.exe")) // standard path on german xp-systems
                    Settings.FsPath = "C:\\Programme\\Microsoft F#\\v4.0";

                while (!File.Exists(Settings.FsPath + "\\Fsi.exe") || Settings.FsPath == "")
                {
                    var options = new Options();
                    if (options.ShowDialog() == false)
                        return;
                }
            }

            var fsiStart = new ProcessStartInfo
                               {
                CreateNoWindow = true,
                UseShellExecute = false,
                FileName = Settings.FsPath + "\\Fsi.exe",
                RedirectStandardError = true,
                RedirectStandardInput = true,
                RedirectStandardOutput = true,
                ErrorDialog = false
            };

            _fsi = new Process {StartInfo = fsiStart, EnableRaisingEvents = true};
            _fsi.Start();

            Func<string, string> cleanUp = str => System.Text.RegularExpressions.Regex.Replace(str, @"^([\s]*>[\s]*)+", string.Empty) + Environment.NewLine;

            _fsi.ErrorDataReceived += (s, e) =>
            {
                Thread.Sleep(100);

                Action act = () => tbOutput.AppendText(e.Data + Environment.NewLine);
                Dispatcher.Invoke(act, System.Windows.Threading.DispatcherPriority.Input);
            };

            _fsi.OutputDataReceived += (s, e) =>
            {
                if (e.Data == null)
                    return;
                Action act = () => tbOutput.AppendText(cleanUp(e.Data));
                Dispatcher.Invoke(act, System.Windows.Threading.DispatcherPriority.Input);
            };

            _fsi.BeginErrorReadLine();
            _fsi.BeginOutputReadLine();
        }
        #endregion

        #region WindowEvents
        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            // init avaEdit
            avaEditor.Options.AllowScrollBelowDocument = true;
            avaEditor.Options.ConvertTabsToSpaces = true;
            avaEditor.Options.EnableEmailHyperlinks = false;
            avaEditor.Options.EnableHyperlinks = false;
            avaEditor.ShowLineNumbers = true;
            // Syntax highlighting
            HighlightingManager.Instance.RegisterHighlighting("F#", new[] { ".fsx" },
                ICSharpCode.AvalonEdit.Highlighting.Xshd
                    .HighlightingLoader.Load(ICSharpCode.AvalonEdit.Highlighting.Xshd
                        .HighlightingLoader.LoadXshd(new XmlTextReader(new StreamReader(new MemoryStream(Properties.Resources.FsHighlighting)))),
                            HighlightingManager.Instance));
            avaEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("F#");

            // restore settings
            Settings.LoadValues();
            // restore layout
            ChangeLayout(Settings.WindowLayout);
            comboLayoutGallery.SelectedItem = FindName(Settings.WindowLayout.ToString());

            // init dialogs
            _openFileDialog = new OpenFileDialog
                                  {
                                      Filter = "F# script files (*.fsx)|*.fsx|F# files (*.fs)|*.fs|All Files (*.*)|*.*"
                                  };
            _openFileDialog.FileOk += _openFileDialog_FileOk;

            _saveFileDialog = new SaveFileDialog
                                  {
                                      Filter = "F# script files (*.fsx)|*.fsx|F# files (*.fs)|*.fs|All Files (*.*)|*.*"
                                  };
            _saveFileDialog.FileOk += _saveFileDialog_FileOk;

            PrepareFsi();

            LoadRecentDocuments();
        }

        private void btnRestart_Click(object sender, RoutedEventArgs e)
        {
            PrepareFsi();
        }

        private void btnPastebin_Click(object sender, RoutedEventArgs e)
        {
            Pastebin pastebinWin = new Pastebin();
            pastebinWin.Initialize(avaEditor.Text);
            pastebinWin.Show();
        }

        private void avaEditor_Drop(object sender, System.Windows.DragEventArgs e)
        {
            string filename = (e.Data.GetData(DataFormats.FileDrop, true) as string[])[0];
            if (Path.GetExtension(filename).ToUpper().StartsWith(".FS")) // fsx is also included
            {
                if (_scriptChanged)
                {
                    var question = System.Windows.Forms.MessageBox.Show(Properties.Resources.MainWindow_btnopen_Click_OpenAnyway, "Funtastic",
                        MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
                    if (question == System.Windows.Forms.DialogResult.No)
                        return;
                }

                OpenFile(filename);
                RefreshWindowAfterOpen(filename);
            }
        }

        private void LoadRecentDocuments()
        {
            recentItems.ItemsSource = Settings.RecentDocuments;
        }

        private void btnChangeZoom_Click(object sender, RoutedEventArgs e)
        {
            double factor = 0;
            Double.TryParse(zoomTb.Text, out factor);
            if (factor < 1)
                return;
            avaEditor.FontSize = factor;
            tbOutput.FontSize = factor;
        }

        private void end_Click(object sender, RoutedEventArgs e)
        {
            Close();
        }

        void comboLayoutGallery_SelectionChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            var selectedText = (string) comboLayoutGallery.SelectedValue;
            switch (selectedText)
            {
                case "vertical stacking":
                    Settings.WindowLayout = Layout.Vertical;
                    ChangeLayout(Layout.Vertical);
                    break;
                case "horizontal stacking":
                    Settings.WindowLayout = Layout.Horizontal;
                    ChangeLayout(Layout.Horizontal);
                    break;
                case "only code":
                    Settings.WindowLayout = Layout.Hidden;
                    ChangeLayout(Layout.Hidden);
                    break;
            }
        }

        // maybe funtastic needs states ;)
        private void ChangeLayout(Layout input)
        {
            switch (input)
            {
                case Layout.Vertical:
                    // avaEditor:   rowspan 1, row 0, columnspan 2, column 0
                    // tboutput:    rowspan 1, row 1, columnspan 2, column 0
                    // GridSplitter:rowspan 1, row 0, columnspan 2, column 0, height 4, width auto, resizedirection rows
                    GridSplitter.Visibility = System.Windows.Visibility.Visible;
                    tbOutput.Visibility = System.Windows.Visibility.Visible;

                    Grid.SetRowSpan(avaEditor, 1);
                    Grid.SetColumnSpan(avaEditor, 2);

                    Grid.SetRowSpan(tbOutput, 1);
                    Grid.SetRow(tbOutput, 1);
                    Grid.SetColumnSpan(tbOutput, 2);
                    Grid.SetColumn(tbOutput, 0);

                    Grid.SetRowSpan(GridSplitter, 1);
                    Grid.SetColumnSpan(GridSplitter, 2);
                    GridSplitter.Height = 4.0d;
                    GridSplitter.Width = 2000; // Double.NaN does not work
                    GridSplitter.ResizeDirection = GridResizeDirection.Rows;
                    break;
                case Layout.Horizontal:
                    // avaEditor:   rowspan 2, row 0, columnspan 1, column 0
                    // tboutput:    rowspan 2, row 0, columnspan 1, column 1
                    // GridSplitter:rowspan 2, row 0, columnspan 1, column 0, height auto, width 4, resizedirection columns
                    GridSplitter.Visibility = System.Windows.Visibility.Visible;
                    tbOutput.Visibility = System.Windows.Visibility.Visible;

                    Grid.SetRowSpan(avaEditor, 2);
                    Grid.SetColumnSpan(avaEditor, 1);

                    Grid.SetRowSpan(tbOutput, 2);
                    Grid.SetRow(tbOutput, 0);
                    Grid.SetColumnSpan(tbOutput, 1);
                    Grid.SetColumn(tbOutput, 1);

                    Grid.SetRowSpan(GridSplitter, 2);
                    Grid.SetColumnSpan(GridSplitter, 1);
                    GridSplitter.Height = 2000; // Double.NaN does not work
                    GridSplitter.Width = 4.0d;
                    GridSplitter.ResizeDirection = GridResizeDirection.Columns;
                    break;
                case Layout.Hidden:
                    // avaEditor:   rowspan 2, row 0, columnspan 2, column 0
                    // tboutput:    hidden
                    // GridSplitter:hidden
                    GridSplitter.Visibility = System.Windows.Visibility.Hidden;
                    tbOutput.Visibility = System.Windows.Visibility.Hidden;
                    Grid.SetRowSpan(avaEditor, 2);
                    Grid.SetColumnSpan(avaEditor, 2);
                    break;
            }
        }

        void tbOutput_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
        {
            tbOutput.ScrollToEnd();
        }

        private void mainWindow_KeyDown(object sender, KeyEventArgs e)
        {
            // convert tab to 4 spaces
            if (e.Key == Key.Tab)
            {
                e.Handled = true;
                var flag = avaEditor.SelectionStart;
                avaEditor.Text = avaEditor.Text.Insert(flag, Space4);
                avaEditor.SelectionStart = flag + 4;
            }

            _previousKey = _currentKey;
            _currentKey = e.SystemKey;

            if (_previousKey.HasValue && _currentKey.HasValue)
            {
                var combination = _previousKey.Value | _currentKey.Value;

                const Key st1 = Key.LeftAlt | Key.Return;
                const Key st2 = Key.RightAlt | Key.Return;

                if (combination == st1 || combination == st2)
                {
                    e.Handled = true;
                    SendSelectedCodeToFsi();
                }

                _previousKey = null;
                _currentKey = null;
            }
        }

        void avaEditor_TextChanged(object sender, EventArgs e)
        {
            _scriptChanged = true;
        }

        private void btnInteractive_Click(object sender, RoutedEventArgs e)
        {
            if (avaEditor.SelectedText == "")
                SendCodeToFsi();
            else
                SendSelectedCodeToFsi();
        }

        private void RibbonButton_Click(object sender, RoutedEventArgs e)
        {
            var options = new Options();
            options.ShowDialog();
        }

        private void btnClearOutput_Click(object sender, RoutedEventArgs e)
        {
            tbOutput.Clear();
        }
        #endregion

        #region CopyPaste
        private void btnPaste_Click(object sender, RoutedEventArgs e)
        {
            avaEditor.Paste();
        }

        private void btnCopy_Click(object sender, RoutedEventArgs e)
        {
            avaEditor.Copy();
        }

        private void btnCut_Click(object sender, RoutedEventArgs e)
        {
            avaEditor.Cut();
        }

        private void btnBack_Click(object sender, RoutedEventArgs e)
        {
            avaEditor.Undo();
        }

        private void btnForeward_Click(object sender, RoutedEventArgs e)
        {
            avaEditor.Redo();
        }
        #endregion
    }
}
